#include "icache/avi_reader.h"
#include "icache/avi_frame_id.h"
#include "icache/disc_cache.h"
#include "icache/mem_cache.h"
#include "icache/subscribe.h"
#include "imglib/avi.h"
#include "mswin/file_dir.h"
#include "xml/stream.h"

#ifdef max
#undef max
#endif

namespace icache {

// avi_read_order

std::ostream & operator << (std::ostream & s, avi_read_order const & o)
{
	return s << '[' << o.desired.first << " [" << o.required.first << ',' << o.required.last << ") " << o.desired.last << ')';
}

// avi_read_order_queue

bool avi_read_order_queue::Push(avi_read_order o) 
{
	//D std::cout << "\nbefore new order: " << o << '\n';
	//D DequeType::iterator k;
	//D for (k = m_Queue.begin(); k != m_Queue.end(); ++k)
	//D 	std::cout << *k << '\n';

	DequeType::reverse_iterator i = m_Queue.rbegin();
	DequeType::reverse_iterator j = m_Queue.rbegin();
	//join all intersecting orders in one superorder
	while (i != m_Queue.rend()) {
		if (o.intersecting(*i)) {
			o.unite(*i);
			m_Queue.erase(--(i.base()));
			j = i; ///< insert new order not farther than erased order
			++i;
		}
		else
			++i;
	}

	//D std::cout << "middle:\n";
	//D for (k = m_Queue.begin(); k != m_Queue.end(); ++k)
	//D 	std::cout << *k << '\n';

	while (j != m_Queue.rend() && j->priority < o.priority)
		++j;
	m_Queue.insert(j.base(), o);

	//D std::cout << "after:\n";
	//D for (k = m_Queue.begin(); k != m_Queue.end(); ++k)
	//D 	std::cout << *k << '\n';

	return true;
}

//avi reader

avi_reader::avi_reader(const string & filename, icache::coordinator * c, int prefetch) : 
	_subscriptions(new subscriptions),
	_log(0),
	_coordinator(c),
	_prefetch(prefetch)
{
	if (!filename.empty()) {
		try {
			open(filename);
		}
		catch (...) {
			Terminate();
			resume();
			resume();
			wait_finish();
			throw;
		}
	}
}

avi_reader::~avi_reader()
{
	//thread should terminate before destruction will take place
	close();
	Terminate();
	resume(); // Resume twice to be sure that the thread is really resumed,
	resume(); // it's required since the thread is created suspended (and resumed only after successfull inspect_keyframes finish)
			  // and close() method suspends its again.
	wait_finish();
}

///inspect_keyframes is asyncronios continuation of footage opening,
///its aim is to check whether all frames in the footage are key frames
class inspect_keyframes : public mswin::thread
{
public:
	inspect_keyframes(avi_reader & r) : reader(r)
		{ resume(); }
	~inspect_keyframes()
		{ wait_finish(); }
	bool check() {
		img::avi::reader & r = *reader._reader;
		r.jump(0);
		for (int n = 1; n < reader.duration(); ++n) {
			if (r.next_key() != n)
				return false;
			r.jump(n);
		}
		return true;
	}
	virtual void run() {
		reader._all_keyframes = check();
		reader.resume();
	}
private:
	avi_reader & reader;
};

void avi_reader::open(const string & filename)
{
	if (is_open()) {
		WaitIdle();
		suspend();
	}
	auto_lock lock(*this);
	_reader.reset();
	if (_footage.get() == 0)
		_footage.reset(new img::avi::footage());
	_footage->open(filename);
	_basename = mswin::basename(filename);
	_fullpath = mswin::fullpath(filename);
	_reader.reset(new img::avi::reader(*_footage, 0));
	_inspector.reset(new inspect_keyframes(*this));
}

void avi_reader::close()
{
	if (is_open())
		WaitIdle();
	_inspector.reset();
	suspend();
	auto_lock lock(*this);
	_reader.reset();
	if (_footage.get())
		_footage->close();
	_basename.clear();
	_fullpath.clear();
}

bool avi_reader::all_keyframes() const
{ 
	assert(_inspector.get());
	_inspector->wait_finish();
	return _all_keyframes; 
}

int avi_reader::duration() const
{
	assert(is_open());
	return _footage->duration();
}

void avi_reader::request(const OrderType & o) {
	if (o.priority > GetThreadPriority(handle())) 
		SetThreadPriority(handle(), o.priority);
	superclass::Place(o);
}

void avi_reader::request(int frame, nearby may_be_requested_later, int priority)
{
	avi_read_order o;
	o.priority = priority;
	o.desired = o.required = span(frame, frame+1);
	int pr = all_keyframes() ? 1 : _prefetch;
	if (may_be_requested_later & backward) {
		o.desired.first -= pr;
		if (o.desired.first < 0)
			o.desired.first = 0;
	}
	if (may_be_requested_later & forward) {
		o.desired.last += pr;
		if (o.desired.last > duration())
			o.desired.last = duration();
	}
	request(o);
}

void avi_reader::wait_for_frame(int frame)
{
	//if (_log) log() << "\nwaiting for " << frame << '\n';
	subscriber s(*_subscriptions, frame);
	assert(_coordinator);
	if (_coordinator->touch(avi_reader::frame_id(frame)))
		return;
	s.wait();
	//if (_log) log() << "\nstop waiting for " << frame << '\n';
}

void avi_reader::request_and_wait(int frame, nearby may_be_requested_later, int priority)
{
	request(frame, may_be_requested_later, priority);
	wait_for_frame(frame);
}

void avi_reader::Execute(const OrderType & o)
{
	mswin::thread::set_debug_name("icache::avi_reader");
	//high priority orders were already processed?
	if (o.priority < GetThreadPriority(handle())) 
		SetThreadPriority(handle(), o.priority);

	assert(_coordinator);
	mem_cache * mc = _coordinator->mc;
	disc_cache * dc = _coordinator->dc;

	if (_all_keyframes) {
		if (_log)
			log() << '\n' << o << '\n';
		assert(mc);
		for (int frame = o.desired.first; frame != o.desired.last; ++frame) {
			item_id_sptr id(avi_frame_id::create(*this, frame));
			if (!mc->touch(id)) {
				_reader->jump(frame);
				_reader->read_next(true);
				assert(_reader->last() == frame);
				if (_log)
					log() << ' ' << frame; 
				mc->put(cached_image::create(id, _reader->frame()));
			}
			_subscriptions->event(frame);
		}
	}
	else {
		//!_all_keyframes
		assert(dc);
		int begin = std::max(o.desired.first, _reader->nearest_keyframe_to(o.required.first));
		int end = _reader->nearest_keyframe_to(o.desired.last-1);
		if (end < o.required.last || end <= begin)
			end = o.desired.last;

		if (_log)
			log() << '\n' << o << " -> [" << begin << ',' << end << ")\n";
		for (int frame = begin; frame != end; ++frame) {
			item_id_sptr id(avi_frame_id::create(*this, frame));
			if (!dc->touch(id)) {
				_reader->jump(frame);
				while (_reader->last() != frame) {
					_reader->read_next(true);
					int f = _reader->last();
					if (_log)
						log() << ' ' << f; 
					if (f < begin) {
						//put in cache only if it's empty
						item_id_sptr id(avi_frame_id::create(*this, f));
						if (!dc->touch(id, dont_change_lru_position)) {
							cached_image_sptr ci(cached_image::create(id, _reader->frame()));
							if (_subscriptions->on_frame(f)) {
								if (_log) log() << 'w';
								if (mc) mc->put(ci);
								dc->put(ci); 
							}
							else
								dc->put(ci, least_recently_used); 
						}
						_subscriptions->event(f);
					}
				}
				cached_image_sptr ci(cached_image::create(id, _reader->frame()));
				dc->put(ci);
				if (_subscriptions->on_frame(frame)) {
					if (_log) log() << 'w';
					if (mc) mc->put(ci);
				}
			}
			_subscriptions->event(frame);
		}
	}
}

string avi_reader::description() const
{
	return string("AVI file '") + _fullpath + "'";
}

int avi_reader::size() const
{
	return duration();
}

item_id_sptr avi_reader::frame_id(int frame)
{
	return avi_frame_id::create(*this, frame);
}

cached_image_sptr avi_reader::get(int frame, nearby may_be_requested_later, bool wait)
{
	item_id_sptr request(avi_frame_request::create(*this, frame, may_be_requested_later, 
		GetThreadPriority(GetCurrentThread())));
	if (wait)
		return _coordinator->get(request);
	else {
		cached_image_sptr p = try_get(frame);
		if (!p)
			request->request();
		return p;
	}
}

cached_image_sptr avi_reader::try_get(int frame)
{
	return _coordinator->try_get(avi_frame_id::create(*this, frame));
}

icache::cache * avi_reader::cache() const
{
	return _coordinator;
}

int avi_reader::first_frame_num() const
{
	return 0;
}

size_extent avi_reader::dim() const 
{
	return _reader->dim();
}

bool avi_reader::alpha_channel() const
{
	return false;
}

fraction avi_reader::fps() const
{
	return _reader->fps();
}

string avi_reader::filepath() const
{
	return _fullpath;
}

void avi_reader::coordinator(icache::coordinator * c) {
	if (is_open())
		WaitIdle();
	_coordinator = c;
}

xml::stream & avi_reader::serialize(xml::stream & s, string const & reference_path) 
{
	xml::node & r = s.root();
	if (s.out()) {
		assert(is_open());
		r["type"] = "avi";
		r["path"] = mswin::relative(_fullpath, reference_path);
	}
	else {
		if ((string)r["type"] != "avi")
			throw exception("couldn't initialize AVI from XML stream");
		open(mswin::absolute(r["path"], reference_path));
	}
	return s;
}

} //namespace icache
